package xj.graph2d.tools;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.util.Iterator;
import java.util.Observer;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.border.Border;

import xj.graph2d.Command;
import xj.graph2d.Document;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.DrawShapeMessage;
import xj.graph2d.DrawShapeSearch;
import xj.graph2d.GraphComponentShape;
import xj.graph2d.GraphDocument;
import xj.graph2d.GraphEdgeBase;
import xj.graph2d.GraphHyperNodeBase;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.Group;
import xj.graph2d.Label;
import xj.graph2d.LineShape;
import xj.graph2d.Rotatable;
import xj.graph2d.SelfLoop;
import xj.graph2d.TextShape;
import xj.graph2d.TwoEndsShape;
import xj.graph2d.Command.AuxAction;
import xj.graph2d.command.AddVertexCommand;
import xj.graph2d.command.DeltaCommand;
import xj.graph2d.command.EdgeAuxAction;
import xj.graph2d.command.LabelAuxAction;
import xj.graph2d.command.MoveCircumferenceCommand;
import xj.graph2d.command.MoveCommand;
import xj.graph2d.command.MoveCornerCommand;
import xj.graph2d.command.MoveCurveCommand;
import xj.graph2d.command.MoveEdgeCommand;
import xj.graph2d.command.MoveLineEndCommand;
import xj.graph2d.command.MoveVertexCommand;
import xj.graph2d.command.NodeMoveAuxAction;
import xj.graph2d.command.RemoveShapeCommand;
import xj.graph2d.command.RemoveVertexCommand;
import xj.graph2d.command.RotationCommand;
import xj.graph2d.command.SubshapeAuxAction;
import xj.graph2d.graph.GraphNode;
import xj.graph2d.shapes.CurveShape;
import xj.graph2d.shapes.GraphEdgeShape;
import xj.graph2d.shapes.HorizontalCompartments;
import xj.graph2d.shapes.OrthogonalLineShape;
import xj.graph2d.shapes.PolyCurveShape;
import xj.graph2d.shapes.PolyLineShape;
import xj.graph2d.shapes.PolyShape;
import xj.graph2d.shapes.VerticalCompartments;
import xj.util.gui.JPanelPopup;

/**
 *
 * Editing operations: 
 *  Move label inside a node: Alt + L-drag
 *  Add/remove vertex of polyshape: double L-click
 *  Add/remove compartments: double L-click
 *
 */
public class PointTool 
  extends SimpleDrawTool {

  public PointTool() {
  }

  public void start(Observer observer) {
    super.start(observer);

    if (doc instanceof GraphDocument) {
      graph = (GraphDocument) doc;
      editingGraph = (doc.getDocumentType() != 
		      Document.DocumentType.Generic_Diagram);
    }
  }

  @Override
  public void mouseLButtonPressed(Observer observer, Point p, int modifiers) {
    if (debug) {
      System.out.println("PointTool.mouseLButtonPressed p=" + p + 
			 " actionShape == null " + (actionShape == null));
    }

    mousePosition = p;

    if (showOverHint) {
      overShape = null;
    }
    hotspotShape = null;

    mouseButtonDown = true;
    unsetSelection();
    start(observer);
    auxActions.clear();

    if (view != null) {
      displayLocation(p);
      cur = view.physicalToVirtual(p);
      hidePopup();

      setSelection(modifiers);
      if (selectedShape != null && 
	  moveShape != null) {
	doc.setSelectedShape(selectedShape);
	handleShapeSelected(selectedShape);

	DrawShape parent = moveShape.getParent();
	if (parent != null) {
	  pclip = parent.isClipSubShapes();
	  parent.setClipSubShapes(false);
	}

	if (editingGraph) {
	  if (movingNode) {

	  } else if (movingEdge) {
	    if (movingEdgeSource) {
	      graph.disconnectSource(edge);
	      auxActions.add(new EdgeAuxAction(graph, edge, src,
					       EdgeAuxAction.EDGE_SOURCE, 
					       EdgeAuxAction.EDGE_DISCONNECTION));
	    } else if (movingEdgeDestination) {
	      graph.disconnectDestination(edge);
	      auxActions.add(new EdgeAuxAction(graph, edge, des,
					       EdgeAuxAction.EDGE_DESTINATION,
					       EdgeAuxAction.EDGE_DISCONNECTION));
	    } else if (movingEdgeBothEnds) {
	      if (src != null) {
		DrawShape srcParent = src.getParent();
		if (srcParent != null) {
		  srcpclip = srcParent.isClipSubShapes();
		  srcParent.setClipSubShapes(false);
		}
	      }
	      if (des != null) {
		DrawShape desParent = des.getParent();
		if (desParent != null) {
		  despclip = desParent.isClipSubShapes();
		  desParent.setClipSubShapes(false);
		}
	      }
	    }
	  }
	}

	if (selectedShape instanceof TextShape && 
	    textTool != null) {
	  if (debug) {
	    System.out.println("PointTool select TextShape: font=" + 
			       selectedShape.getDrawAttr().getFont() + 
			       " @" + Integer.toHexString(selectedShape.hashCode()) +
			       " (parent == null)=" + (selectedShape.getParent() == null));
	  }

	  textTool.start(observer);
	  textTool.editTextShape((TextShape) selectedShape, prev);
	}
      }

      canvas.clearRepaintShapes();
      canvas.repaint();
    }
  }

  // categories of shapes the needs to be handled differently  
  protected static final int CAT_DEFAULT         = 0;
  protected static final int CAT_POLY_LINE_SHAPE = 1;
  protected static final int CAT_TEXT_SHAPE      = 2;
  protected static final int CAT_CURVE_SHAPE     = 3;
  protected static final int CAT_LINE_SHAPE      = 4;

  protected int getMoveShapeCategory(DrawShape shape) { 
    int cat = CAT_DEFAULT;
    if (shape instanceof PolyShape) {
      cat = CAT_POLY_LINE_SHAPE; 
    } else if (shape instanceof TextShape) {
      cat = CAT_TEXT_SHAPE; 
    } else if (shape instanceof CurveShape) {
      cat = CAT_CURVE_SHAPE;
    } else if (shape instanceof LineShape) {
      cat = CAT_LINE_SHAPE;
    }
    return cat; 
  }

  @Override
  public void mouseDragged(Observer observer, Point p, int modifiers) {
    if (debug) {
      System.out.println("PointTool.mouseLButtonDragged(): p=" + p);
    }

    Point p0 = mousePosition; 
    mousePosition = p;

    if (view != null) {
      displayLocation(p);
      cur = view.physicalToVirtual(p);
      hidePopup();

      if (mouseButtonDown) {
	float px = (float) cur.getX();
	float py = (float) cur.getY();
	float dx = (float) (px - prev.getX());
	float dy = (float) (py - prev.getY());
	boolean altdown = ((modifiers & InputEvent.ALT_DOWN_MASK) != 0);

	if (selectedShape != null && moveShape != null) {
	  if (editingGraph && 
	      movingLabel && 
	      label.isDetachable()) {

	    if (debug) {
	      System.out.println("PointTool.mouseLButtonDragged(): " + 
				 "editingGraph && movingLabel && label.isDetachable()");
	    }

	    GraphEdgeBase comp = graph.searchEdgeHotSpot(cur);
	    if (comp != null) {
	      if (hotspotShape != comp) {
		if (hotspotShape != null) {
		  canvas.removeRepaintShape(hotspotShape);
		}
		hotspotShape = comp;
		canvas.addRepaintShape(hotspotShape, DRAW_HOTSPOT_HINT);
	      }
	    } else {
	      if (hotspotShape != null) {
		canvas.removeRepaintShape(hotspotShape);
	      }
	      hotspotShape = null;
	    }
	  }

	  int cat = CAT_DEFAULT; 
	  if (moveShape instanceof GraphEdgeShape) {
	    GraphEdgeShape eshape = (GraphEdgeShape) moveShape;
	    cat = getMoveShapeCategory(eshape.getEdgeShape());
	  } else { 
	    cat = getMoveShapeCategory(moveShape);
	  }
	  handleMouseDrag(moveShape, cat, px, py, dx, dy, altdown);

	  if (editingGraph) {
	    if (movingNode) {
	      if (debug) {
		System.out.println("PointTool.mouseDragged(): movingNode");
	      }
	      if (adjacentEdges != null) {
		if (debug) {
		  System.out.println("PointTool.mouseDragged(): adjacentEdges != null");
		}
		moveSlefLoop(node, dx, dy);
		GraphDocument.layoutEdges(adjacentEdges);
	      }
	    } else if (movingEdge) {
	      if (movingEdgeBothEnds) {
		if (src != null && !src.isDescendantOf(des)) {
		  src.moveNotify(dx, dy);
		  moveSlefLoop(src, dx, dy);
		}
		if (des != null && des != src && !des.isDescendantOf(src)) {
		  des.moveNotify(dx, dy);
		  moveSlefLoop(des, dx, dy);
		}
	      }
	      if (adjacentEdges != null) {
		GraphDocument.layoutEdges(adjacentEdges);
	      }
	      edge.adjustEnds(!moveCtrPoints);
	      edge.layoutComponents();
	    }
	  }

	  if (command != null) {
	    command.move(dx, dy);
	  }
	} else {
	  // no shape selected
	  // pan canvas

	  if (debug) {
	    System.out.println("PointTool.mouseLButtonDragged(): pan canvas");
	  }

	  view.moveScrollPosition(p0.x - p.x, p0.y - p.y);
	  //view.moveScrollPosition(mousePosition.x - p.x, mousePosition.y - p.y);
	}

	//mousePosition = p;
	prev = cur;
      }
      canvas.repaint();
    }
  }

  protected void handleMouseDrag(DrawShape moveShape, int cat, 
				 float px, float py, float dx, float dy,
				 boolean altdown) { 
    if (cat == CAT_POLY_LINE_SHAPE) {
      if (debug) { 
	System.out.println("PointTool.handleMouseDrag() cat:  CAT_POLY_LINE_SHAPE pos1=" + 
			   Integer.toHexString(pos1));
      }

      PolyShape polyshape = (PolyShape) moveShape;
      if (moveShape instanceof GraphEdgeShape) {
	GraphEdgeShape eshape = (GraphEdgeShape) moveShape;
	polyshape = (PolyShape) eshape.getEdgeShape();
      }
      if (pos1 == DrawShapeConstants.PointOnPolyShapeVertex) {
	// move the vertex
	if (debug) { 
	  System.out.println("PointTool.handleMouseDrag() PointOnPolyShapeVertex");
	}
	//if (polyshape instanceof OrthogonalLineShape) { 
	int n = polyshape.getNumberOfPoints();
	if (pos2 != 0 && pos2 != (n - 1)) { 
	  moveCtrPoints = true;
	}
	polyshape.moveVertex(pos2, dx, dy);
	if (command == null) {
	  command = new MoveVertexCommand(view, doc, polyshape, pos2);
	}
      } else if (pos1 == DrawShapeConstants.PointOnPolyCurveCtrPoint && 
		 polyshape instanceof PolyCurveShape) {
	// PolyCurveShape polycurve = (PolyCurveShape)
	// polyshape;
	if (debug) { 		
	  System.out.println("PointTool.handleMouseDrag() PointOnPolyCurveCtrPoint");
	}
	moveCtrPoints = true;
	polyshape.moveVertex(-pos2, dx, dy);
	if (command == null) {
	  command = new MoveVertexCommand(view, doc, polyshape, -pos2);
	}
      } else if (pos1 == DrawShapeConstants.PointOnPolyShapeEdge) {
	// move the polyshape
	if (debug) { 
	  System.out.println("PointTool.handleMouseDrag() PointOnPolyShapeEdge");
	}
	moveShape.move(dx, dy);
	if (command == null) {
	  command = new MoveCommand(view, doc, moveShape);
	}
      } else if (pos != DrawShapeConstants.PointOutside && 
		 pos != DrawShapeConstants.PointInBounds && 
		 moveShape.isMovable()) {
	// in the interior of polyshape
	// move the polyshape
	if (debug) { 
	  System.out.println("PointTool.handleMouseDrag() interior");
	}

	moveShape.move(dx, dy);
	if (command == null) {
	  command = new MoveCommand(view, doc, moveShape);
	}
      }
    } else if (cat == CAT_TEXT_SHAPE) {
      if (pos1 == DrawShapeConstants.PointOnStringBaselineEnd && 
	  moveShape instanceof Rotatable && 
	  moveShape.isAllowRotation()) {
	// canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
	TextShape text = (TextShape) moveShape;
	if (command == null) {
	  command = new RotationCommand(view, doc, text);
	}
	text.setToRotation(px, py);
      } else if (pos != DrawShapeConstants.PointOutside && 
		 pos != DrawShapeConstants.PointInBounds && 
		 moveShape.isMovable()) {
	dx = moveShape.canMoveX(dx);
	dy = moveShape.canMoveY(dy);
	moveShape.move(dx, dy);
	if (command == null) {
	  command = new MoveCommand(view, doc, moveShape);
	}
      }
    } else if (cat == CAT_CURVE_SHAPE) {
      CurveShape curve = (CurveShape) moveShape;
      if (pos1 == DrawShapeConstants.PointOnCurveEndPoint) {
	if (pos2 == 0) {
	  curve.moveEnd1Adj(dx, dy);
	  if (command == null) {
	    command = new MoveCurveCommand(view, doc, curve,
					   MoveCurveCommand.CURVE_END_1);
	  }
	} else if (pos2 == 1) {
	  curve.moveEnd2Adj(dx, dy);
	  if (command == null) {
	    command = new MoveCurveCommand(view, doc, curve,
					   MoveCurveCommand.CURVE_END_2);
	  }
	}
      } else if (pos1 == DrawShapeConstants.PointOnCurveCtrPoint) {
	moveCtrPoints = true;
	if (pos2 == 0) {
	  curve.moveCtr1(dx, dy);
	  if (command == null) {
	    command = new MoveCurveCommand(view, doc, curve,
					   MoveCurveCommand.CURVE_CTR_1);
	  }
	} else if (pos2 == 1) {
	  curve.moveCtr2(dx, dy);
	  if (command == null) {
	    command = new MoveCurveCommand(view, doc, curve,
					   MoveCurveCommand.CURVE_CTR_2);
	  }
	}
      } else if (pos1 == DrawShapeConstants.PointOnCurve) {
	moveShape.move(dx, dy);
	if (command == null) {
	  command = new MoveCommand(view, doc, moveShape);
	}
      }
    } else if (cat == CAT_LINE_SHAPE) {
      if (pos1 == DrawShapeConstants.PointOnEnd) {
	if (pos2 == 0) {
	  moveShape.moveEnd1(dx, dy);
	  if (command == null) {
	    command = new MoveLineEndCommand(view, doc, moveShape,
					     MoveLineEndCommand.LINE_END_1);
	  }
	} else if (pos2 == 1) {
	  moveShape.moveEnd2(dx, dy);
	  if (command == null) {
	    command = new MoveLineEndCommand(view, doc, moveShape,
					     MoveLineEndCommand.LINE_END_2);
	  }
	}
      } else if (pos1 == DrawShapeConstants.PointInside) {
	moveShape.move(dx, dy);
	if (command == null) {
	  command = new MoveCommand(view, doc, moveShape);
	}
      }
    } else {
      // cat == CAT_DEFAULT
      if ((pos1 == DrawShapeConstants.PointNECorner || 
	   pos1 == DrawShapeConstants.PointNWCorner || 
	   pos1 == DrawShapeConstants.PointSECorner || 
	   pos1 == DrawShapeConstants.PointSWCorner) && 
	  moveShape.isCornorSensitive()) {

	if (debug) {
	  System.out.println("PointTool.mouseDragged() move corner: pos1=" + pos1);
	}

	if (moveShape.isFixedRatio()) {
	  float adx = Math.abs(dx);
	  float ady = Math.abs(dy);
	  if (adx != ady) {
	    float ad = (adx + ady) / 2;
	    dx = (dx >= 0 ? ad : -ad);
	    dy = (dy >= 0 ? ad : -ad);
	  }
	}

	boolean scaleSubshapes = altdown;
	moveShape.moveCorner(pos1, dx, dy, scaleSubshapes);
	if (command == null) {
	  command = new MoveCornerCommand(view, doc, moveShape, pos1,
					  scaleSubshapes);
	}
      } else if ((pos1 == DrawShapeConstants.PointNorthEdge || 
		  pos1 == PointSouthEdge || 
		  pos1 == PointEastEdge || 
		  pos1 == PointWestEdge) && 
		 !moveShape.isFixedRatio() && 
		 moveShape.isEdgeSensitive()) {

	if (debug) {
	  System.out.println("PointTool() move edge: pos1=" + pos1);
	}

	boolean scaleSubshapes = altdown;
	moveShape.moveEdge(pos1, dx, dy, scaleSubshapes);
	if (command == null) {
	  command = new MoveEdgeCommand(view, doc, moveShape, pos1,
					scaleSubshapes);
	}
      } else if (pos1 == PointOnCircumference && 
		 moveShape.isResizable()) {
	if (command == null) {
	  command = new MoveCircumferenceCommand(view, doc, moveShape,
						 px, py);
	}
	moveShape.moveCircumference(px, py);
      } else if (pos1 == PointOnRotationHandle && 
		 moveShape instanceof Rotatable && 
		 moveShape.isAllowRotation()) {

	if (debug) { 
	  System.out.println("PointTool.mouseDragged()  rotation handle: pos1=" + pos1);
	}

	if (command == null) {
	  command = new RotationCommand(view, doc, moveShape);
	}
	moveShape.setToRotation(px, py);
      } else if (pos1 == PointOnHorizontalDivider) {
	if (moveShape instanceof HorizontalCompartments) {
	  HorizontalCompartments hshape = (HorizontalCompartments) moveShape;
	  hshape.moveHorizontalDivider(pos2, dy);
	}
      } else if (pos1 == PointOnVerticalDivider) {
	if (moveShape instanceof VerticalCompartments) {
	  VerticalCompartments vshape = (VerticalCompartments) moveShape;
	  vshape.moveVerticalDivider(pos2, dx);
	}
      } else if (pos != PointOutside && 
		 pos != PointInBounds && moveShape.isMovable()) {
	dx = moveShape.canMoveX(dx);
	dy = moveShape.canMoveY(dy);
	moveShape.move(dx, dy);
	if (command == null) {
	  command = new MoveCommand(view, doc, moveShape);
	}
      }
    }
  }

  public void mouseLButtonReleased(Observer observer, Point p, int modifiers) {
    if (debug) {
      //System.out.println("PointTool.mouseLButtonReleased p=" + p);
      if (moveShape != null) { 
	System.out.println("PointTool.mouseLButtonReleased() @1 moveShape=" + 
			   moveShape.getBounds() + " cur=" + cur);
      }
    }

    mousePosition = p;

    if (mouseButtonDown && 
	selectedShape != null && 
	moveShape != null && 
	view != null) {
      hidePopup();

      moveShape.normalize();

      if (!movingLabel && !movingEdge) {
	if (debug) {
	  System.out.println("PointTool.mouseLButtonReleased(): !movingLabe && !movingEdge");
	}

	moveShape.doLayout();

	DrawShape parent = moveShape.getParent();
	DrawShape newParent = doc.search(cur, moveShape);
	if (parent != null) {
	  parent.setClipSubShapes(pclip);
	}
	if (newParent != null) {
	  if (debug) System.out.println("PointTool.mouseLButtonReleased(): newParent != null newParent=" + newParent);	  

	  // find an ancestor that accepts the moveShape 
	  DrawShape pp = newParent;
	  while (pp != null && 
		 !pp.isAcceptSubshape(moveShape)) {
	    pp = pp.getParent(); 
	  }
	  if (pp != null) {
	    newParent = pp;
	  }

	  if (debug) {
	    System.out.println("PointTool.mouseLButtonReleased(): newParent != null newParent=" + newParent);	  
	    System.out.println("PointTool.mouseLButtonReleased(): newParent.isAcceptSubshape() " + 
			       newParent.isAcceptSubshape(moveShape));
	  }

	  if (parent == null) {
	    if (debug) {
	      System.out.println("PointTool.mouseLButtonReleased(): parent == null");
	    }

	    if (newParent.isAcceptSubshape(moveShape)) {
	      boolean acceptSubshape = true;
	      if (editingGraph && 
		  graph != null &&
		  newParent instanceof GraphNodeBase && 
		  moveShape instanceof GraphNodeBase) { 
		acceptSubshape = graph.checkAcceptSubnode((GraphNodeBase) newParent, 
							  (GraphNodeBase) moveShape);
	      }

	      if (debug) {
		System.out.println("PointTool.mouseLButtonReleased(): acceptSubshape " + 
				   acceptSubshape);
	      }

	      if (acceptSubshape) { 
		if (debug) {
		  System.out.println("PointTool.mouseLButtonReleased(): newParent.isAcceptSubshape()");
		}
		// attcahe to new parent
		if (graph != null) {
		  graph.remove(moveShape, false);
		} else {
		  doc.remove(moveShape);
		}
		newParent.addShape(moveShape);
		doc.shapeAdded(moveShape);
		auxActions.add(new SubshapeAuxAction(doc, null, newParent,
						     moveShape, 
						     SubshapeAuxAction.ATTACH_SUBSHAPE));
	      }
	    }
	  } else {
	    if (debug) {
	      System.out.println("PointTool.mouseLButtonReleased(): parent != null parent=" + parent);
	    }
	    if (newParent != parent) {
	      if (debug) {
		System.out.println("PointTool.mouseLButtonReleased(): newParent != parent");
	      }
	      // detach and attach
	      if (moveShape.isDetachable()) {
		if (debug) {
		  System.out.println("PointTool.mouseLButtonReleased(): moveShape.isDetachable()");
		}
		if (parent instanceof GraphHyperNodeBase) {
		  GraphHyperNodeBase hparent = (GraphHyperNodeBase) parent;
		  hparent.removeShape(moveShape, false);
		} else {
		  parent.removeShape(moveShape);
		}
		doc.shapeRemoved(moveShape);
		if (newParent.isAcceptSubshape(moveShape)) {
		  newParent.addShape(moveShape);
		  doc.shapeAdded(moveShape);
		  auxActions.add(new SubshapeAuxAction(doc, parent, newParent,
						       moveShape, 
						       SubshapeAuxAction.DETACH_ATTACH_SUBSHAPE));
		} else {
		  doc.add(moveShape);
		  auxActions.add(new SubshapeAuxAction(doc, parent, null,
						       moveShape, 
						       SubshapeAuxAction.DETACH_SUBSHAPE));
		}
	      }
	    } else {
	      // newParent == parent
	      if (debug) {
		System.out.println("PointTool.mouseLButtonReleased(): newParent == parent");
	      }

	    }
	  }
	  newParent.componentDimensionChanged(moveShape);
	} else {
	  if (debug) {
	    System.out.println("PointTool.mouseLButtonReleased(): newParent == null");
	  }

	  if (parent != null) {
	    // detach from parent
	    if (moveShape.isDetachable()) {
	      if (parent instanceof GraphHyperNodeBase) {
		GraphHyperNodeBase hparent = (GraphHyperNodeBase) parent;
		hparent.removeShape(moveShape, false);
	      } else {
		parent.removeShape(moveShape);
	      }

	      doc.shapeRemoved(moveShape);
	      doc.add(moveShape);
	      auxActions.add(new SubshapeAuxAction(doc, parent, null,
						   moveShape, 
						   SubshapeAuxAction.DETACH_SUBSHAPE));
	    }
	  }
	}
	
      }

      if (editingGraph) {
	if (debug) {
	  System.out.println("PointTool.mouseLButtonReleased(): editingGraph expansionIcon=" + expansionIcon);
	}
	if (movingLabel) {
	  if (debug) {
	    System.out.println("PointTool.mouseLButtonReleased(): movingLabel");
	  }
	  DrawShape parent = label.getParent();
	  GraphComponentShape parentComp = null;
	  if (parent instanceof GraphComponentShape) {
	    parentComp = (GraphComponentShape) parent;
	  }

	  GraphComponentShape newParent = null;
	  int role = ROLE_DEFAULT;
	  if (parent == null || label.isDetachable()) {
	    if (debug) {
	      System.out.println("PointTool.mouseLButtonReleased(): label.isDetachable()");
	    }

	    newParent = graph.searchHotSpot(cur);
	    if (newParent != null) {
	      int hotspot = newParent.isOnHotSpot(cur);
	      role = GraphComponentShape.hotspotToRole(hotspot);
	    }
	  } else {
	    if (debug) {
	      System.out.println("PointTool.mouseLButtonReleased(): !label.isDetachable()");
	    }

	    newParent = parentComp;
	    role = label.getRole();
	  }

	  if (newParent != null) {
	    if (debug) {
	      System.out.println("PointTool.mouseLButtonReleased(): newParent != null");
	    }
	    if (parent == null) {
	      if (debug) {
		System.out.println("PointTool.mouseLButtonReleased(): parent == null");
	      }
	      // attach label
	      doc.remove(label);
	      Label label1 = newParent.getLabelShape(role);
	      if (label1 == null || label1.isDetachable()) {
		if (debug) {
		  System.out.println("PointTool.mouseLButtonReleased(): role=" + 
				     Integer.toHexString(role) + "  " + 
				     (label1 == null ? "label1 == null"	: "label1.isDetachable()"));
		}

		newParent.setLabel(role, label);
		if (label1 != null) {
		  doc.shapeRemoved(label1);
		}
		doc.shapeAdded(label);
		auxActions.add(new LabelAuxAction(doc, null, newParent, label,
						  label1, -1, role, LabelAuxAction.ATTACH_LABEL));
	      }
	    } else {
	      if (debug) {
		System.out.println("PointTool.mouseLButtonReleased(): parent != null");
	      }
	      int role1 = label.getRole();
	      if (newParent != parent || role1 != role) {
		if (debug) {
		  System.out.println("PointTool.mouseLButtonReleased(): newParent != parent");
		}
		// detach and attach
		boolean canDetach = true;
		if (parentComp != null) {
		  if (label.isDetachable()) {
		    parentComp.removeLabel(role1);
		  } else {
		    parentComp.setLabelOffset(role1);
		    canDetach = false;
		  }
		} else {
		  parent.removeShape(label);
		}

		if (canDetach) {
		  doc.shapeRemoved(label);
		  Label label1 = newParent.getLabelShape(role);
		  if (label1 == null || label1.isDetachable()) {
		    if (debug) {
		      System.out.println("PointTool.mouseLButtonReleased(): role="
					 + Integer.toHexString(role)
					 + "  "
					 + (label1 == null ? "label1 == null"
					    : "label1.isDetachable()"));
		    }

		    newParent.setLabel(role, label);
		    if (label1 != null) {
		      doc.shapeRemoved(label1);
		    }
		    doc.shapeAdded(label);
		    auxActions.add(new LabelAuxAction(doc, parentComp,
						      newParent, label, label1, role1, role,
						      LabelAuxAction.DETACH_ATTACH_LABEL));
		  }
		}
	      } else {
		if (debug) {
		  System.out.println("PointTool.mouseLButtonReleased(): newParent == parent");
		}

		if (parentComp != null) {
		  // move within the same node, adjust label
		  // offset
		  if (label.getRole() == role) {
		    parentComp.setLabelOffset(role);
		  }
		}
	      }
	    }
	  } else {
	    // newParent == null
	    if (debug) {
	      System.out.println("PointTool.mouseLButtonReleased(): newParent == null");
	    }
	    if (parentComp != null) {
	      if (debug) {
		System.out.println("PointTool.mouseLButtonReleased(): parentComp != null");
	      }
	      if (label.isDetachable()) {
		if (debug) {
		  System.out.println("PointTool.mouseLButtonReleased(): label.isDetachable()");
		}
		// detach label from parent
		int role1 = label.getRole();
		parentComp.removeLabel(role1);
		doc.shapeRemoved(label);
		doc.add(label);
		auxActions.add(new LabelAuxAction(doc, parentComp, null, label,
						  null, role1, -1, LabelAuxAction.DETACH_LABEL));
	      } else {
		if (debug) {
		  System.out.println("PointTool.mouseLButtonReleased(): !label.isDetachable()");
		}
		parentComp.setLabelOffset(label.getRole());
	      }
	    }
	  }

	} else if (expansionIcon) { 
	  if (debug) { 
	    System.out.println("PointTool.mouseLButtonReleased(): expansionIcon overShape==null " + 
			       (overShape == null));
	  }

	  GraphHyperNodeBase hnode = null;
	  if (overShape instanceof GraphHyperNodeBase) { 
	    hnode = (GraphHyperNodeBase) overShape;
	  } else if (selectedShape instanceof GraphHyperNodeBase) { 
	    hnode = (GraphHyperNodeBase) selectedShape;
	  }
	  if (hnode != null) { 
	    if (hnode.isExpanded()) { 
	      hnode.collapse(); 
	    } else { 
	      hnode.expand(); 
	    }
	  }
	} else if (onIcon) { 
	  if (overShape != null) { 
	    handleOnIcon(overShape, pos2); 
	  } else { 
	    handleOnIcon(selectedShape, pos2); 
	  }
	} else if (movingNode) {
	  if (debug) {
	    System.out.println("PointTool.mouseLButtonReleased(): movingNode");
	  }
	  connectNodeToDanglingEdges(node);
	  DrawShape pn = node.getParent();

	  if (debug) {
	    System.out.println("PointTool.mouseLButtonReleased(): movingNode pn=" + pn);
	  }

	  if (pn instanceof GraphNodeBase) {
	    GraphNodeBase pnode = (GraphNodeBase) pn;
	    if (node.isOnBoundary()) {
	      pnode.positionNodeOnBoundary(node);
	    } else if (node.getRole() == ROLE_NODE_TEMPLATE) { 
	      pnode.positionTemplateShape();
	    } else {
	      if (pnode.positionSubNode(node, cur)) { 
		doc.shapeChanged(pnode);
	      }
	    }
	  }
	  if (adjacentEdges != null) {
	    GraphDocument.layoutEdges(adjacentEdges);
	  }
	} else if (movingEdge) {
	  if (debug) {
	    System.out.println("PointTool.mouseLButtonReleased(): movingEdge");
	  }
	  if (movingEdgeBothEnds) {
	    if (debug) {
	      System.out.println("PointTool.mouseLButtonReleased(): movingEdgeBothEnds");
	    }
	    if (src == null) {
	      // reconnect
	      Point2D p1 = moveShape.getEnd1();
	      GraphNodeBase node = graph.searchNode(p1);
	      if (node != null) {
		graph.connectSource(edge, node);
		auxActions.add(new EdgeAuxAction(graph, edge, node,
						 EdgeAuxAction.EDGE_SOURCE, 
						 EdgeAuxAction.EDGE_CONNECTION));
	      }
	    } else {
	      if (command != null) {
		auxActions.add(new NodeMoveAuxAction(graph, src,
						     command.getX(), command.getY()));
	      }
	      connectNodeToDanglingEdges(src);
	      DrawShape srcParent = src.getParent();
	      if (srcParent != null) {
		srcParent.setClipSubShapes(srcpclip);
		if (srcParent instanceof GraphNodeBase) {
		  GraphNodeBase pnode = (GraphNodeBase) srcParent;
		  if (src.isOnBoundary()) {
		    pnode.positionNodeOnBoundary(src);
		  } else {
		    if (pnode.positionSubNode(src)) { 
		      doc.shapeChanged(pnode);
		    }
		  }
		}
	      }
	    }

	    if (des == null) {
	      // reconnect
	      Point2D p2 = moveShape.getEnd2();
	      GraphNodeBase node = graph.searchNode(p2);
	      if (node != null) {
		graph.connectDestination(edge, node);
		auxActions.add(new EdgeAuxAction(graph, edge, node,
						 EdgeAuxAction.EDGE_DESTINATION,
						 EdgeAuxAction.EDGE_CONNECTION));
	      }
	    } else {
	      if (des != src) {
		if (command != null) {
		  auxActions.add(new NodeMoveAuxAction(graph, des, command.getX(), command.getY()));
		}
		connectNodeToDanglingEdges(des);
		DrawShape desParent = des.getParent();
		if (desParent != null) {
		  desParent.setClipSubShapes(despclip);
		  if (desParent instanceof GraphNodeBase) {
		    GraphNodeBase pnode = (GraphNodeBase) desParent;
		    if (des.isOnBoundary()) {
		      pnode.positionNodeOnBoundary(des);
		    } else {
		      if (pnode.positionSubNode(des)) { 
			doc.shapeChanged(pnode);
		      }
		    }
		  }
		}
	      }
	    }
	  } else {
	    if (movingEdgeSource || movingEdgeDestination) {
	      if (debug) {
		System.out.println("PointTool.mouseLButtonReleased(): movingEdgeSource | Destination");
	      }
	      // reconnect
	      GraphNodeBase newNode = graph.searchNode(cur);
	      if (newNode != null) {
		if (debug) {
		  System.out.println("PointTool.mouseLButtonReleased(): newNode != null");
		}
		if (movingEdgeSource) {
		  if (debug) {
		    System.out.println("PointTool.mouseLButtonReleased(): movingEdgeSource");
		  }
		  // moveShape.setEnd1((float) cur.getX(), (float) cur.getY());
		  graph.connectSource(edge, newNode);
		  auxActions.add(new EdgeAuxAction(graph, edge, newNode,
						   EdgeAuxAction.EDGE_SOURCE,
						   EdgeAuxAction.EDGE_CONNECTION));
		} else {
		  if (debug) {
		    System.out.println("PointTool.mouseLButtonReleased(): Destination");
		  }
		  // moveShape.setEnd2((float) cur.getX(), (float) cur.getY());
		  graph.connectDestination(edge, newNode);
		  auxActions.add(new EdgeAuxAction(graph, edge, newNode,
						   EdgeAuxAction.EDGE_DESTINATION,
						   EdgeAuxAction.EDGE_CONNECTION));
		}
	      }

	    }
	  }
	  if (adjacentEdges != null) {
	    GraphDocument.layoutEdges(adjacentEdges);
	  }
	  edge.adjustEnds(!moveCtrPoints);
	  edge.layoutComponents();
	}

	/*
	if (node != null || edge != null) {
	  // ???
	  //selectedShape.doLayout();
	}
	*/
      }

      canvas.repaint();

      if (command != null) {
	command.addAuxActions(auxActions);
	auxActions.clear();
	doc.addCommand(command);
	command = null;
      }

    }

    mouseButtonDown = false;
  }
  
  protected void handleOnIcon(DrawShape shape, int pos) {
    System.out.println("PointTool.handleOnIcon() pos=" + pos);
  }

  protected void connectNodeToDanglingEdges(GraphNodeBase node) {
    if (node != null) {
      for (Iterator iter = graph.edges(); iter.hasNext();) {
	Object o = iter.next();
	if (o instanceof GraphEdgeBase) {
	  GraphEdgeBase e = (GraphEdgeBase) o;
	  GraphNode src = e.getSource();
	  if (src == null) {
	    Point2D p1 = e.getEnd1();
	    if (node.isInside(p1) != PointOutside) {
	      graph.connectSource(e, node);
	      auxActions.add(new EdgeAuxAction(graph, e, node,
					       EdgeAuxAction.EDGE_SOURCE, 
					       EdgeAuxAction.EDGE_CONNECTION));
	      e.adjustEnds(!moveCtrPoints);
	    }
	  }
	  GraphNode des = e.getDestination();
	  if (des == null) {
	    Point2D p2 = e.getEnd2();
	    if (node.isInside(p2) != PointOutside) {
	      graph.connectDestination(e, node);
	      auxActions.add(new EdgeAuxAction(graph, e, node,
					       EdgeAuxAction.EDGE_DESTINATION,
					       EdgeAuxAction.EDGE_CONNECTION));
	      e.adjustEnds(!moveCtrPoints);
	    }
	  }
	}
      }
    }
  }

  /**
   * L-button double-click 
   *   on a vertex of a poly shape --> remove the vertex 
   *   on an edge of a poly shape --> add a vertex
   */
  public void mouseLButtonDoubleClicked(Observer observer, Point p, int modifiers) {
    if (debug) {
      System.out.println("PointTool.mouseLButtonDoubleClicked p=" + p);
    }

    mousePosition = p;

    if (view != null) {
      displayLocation(p);
      cur = view.physicalToVirtual(p);
      hidePopup();

      if (selectedShape != null) {
	if (moveShape instanceof PolyShape) {
	  handleAddRemoveVertex((PolyShape) moveShape);
	  //} else if (moveShape instanceof HorizontalCompartments) {
	  //handleAddRemoveCompartment((HorizontalCompartments) moveShape);
	} else { 
	  handleDoubleClick(moveShape);
	}
      }

      /*
      if (command != null) {
	doc.addCommand(command);
	command = null;
      }
      */
    }
    mouseButtonDown = false;
  }

  protected void handleDoubleClick(DrawShape shape) {}

  protected void refreshMoveShape() {
    if (moveShape != null) {
      moveShape.normalize();
      moveShape.setVisible(true);
      doc.setSelectedShape(selectedShape);
      if (editingGraph) {
	if (movingNode) {
	  if (adjacentEdges != null) {
	    GraphDocument.layoutEdges(adjacentEdges);
	  }
	  
	} else if (movingLabel) {
	  DrawShape parent = label.getParent();
	  if (parent != null && parent instanceof GraphNodeBase) {
	    GraphNodeBase parentNode = (GraphNodeBase) parent;
	    parentNode.resetLabelOffset();
	  }
	}
      }
    }
    canvas.repaint();
  }

  protected void handleAddRemoveVertex(PolyShape polyshape) {
    if (debug)
      System.out.println("PointTool.handleAddRemoveVertex() pos1=" + Integer.toHexString(pos1) + 
			 " pos2=" + pos2);
    
    boolean selfloop = false;
    if (polyshape instanceof GraphEdgeShape) { 
      GraphEdgeShape eshape = (GraphEdgeShape) polyshape;
      GraphEdgeBase edge = eshape.getEdgeShape();
      selfloop = (edge instanceof SelfLoop);
    } else { 
      selfloop = (polyshape instanceof SelfLoop);
    }

    if (polyshape != null && !selfloop) { 
      //System.out.println("PointTool.handleAddRemoveVertex() @1");

      if (pos1 == PointOnPolyShapeVertex && 
	  pos2 != 0 &&
	  polyshape.getNumberOfPoints() > 2) {
	Point2D pp = polyshape.getPoint(pos2 - 1);
	if (pp != null) {
	  //System.out.println("PointTool.handleAddRemoveVertex() @2");
	  polyshape.removePoint(pos2 - 1);
	  polyshape.doLayout();
	  Command cmd = new RemoveVertexCommand(view, doc, polyshape, pos2 - 1, 
						(float) pp.getX(), (float) pp.getY());
	  doc.addCommand(cmd);
	}
      } else if (pos1 == PointOnPolyShapeEdge) {
	//System.out.println("PointTool.handleAddRemoveVertex() @3");
	if (polyshape instanceof OrthogonalLineShape) { 
	  int n = polyshape.getNumberOfPoints();
	  if (pos2 == (n-1)) { 
	    pos2--;
	  }
	}
	float px = (float) cur.getX();
	float py = (float) cur.getY();
	polyshape.addPoint(pos2, px, py);
	polyshape.doLayout();
	Command cmd = new AddVertexCommand(view, doc, polyshape, pos2, px, py);
	doc.addCommand(cmd);
      }
      refreshMoveShape();
    }
  }

  protected void handleAddRemoveCompartment(HorizontalCompartments hc) {
    if (debug) { 
      System.out.println("PointTool.handleAddRemoveCompartment() pos2=" + pos2);
    }
    if (hc != null) { 
      //System.out.println("PointTool.handleAddRemoveCompartment() @1");
      if (!hc.isNumberOfHorizontalCompartmentsFixed()) {
	if (pos1 == PointOnHorizontalDivider) {
	  hc.removeHorizontalDivider(pos2);
	} else if (pos1 == PointInHorizontalCompartment) {
	  hc.addHorizontalDivider(pos2, 0);
	}
      }
      refreshMoveShape();
    }
  }

  /**
   * R-button double-click
   */
  public void mouseRButtonDoubleClicked(Observer observer, Point p,
					int modifiers) {
    if (debug) {
      System.out.println("PointTool.mouseRButtonDoubleClicked p=" + p);
    }

    mousePosition = p;

  }

  @Override
  public void mouseMoved(Observer observer, Point p, int modifiers) {
    if (debug) {
      System.out.println("PointTool.mouseMoved() p=" + p);
    }

    mousePosition = p;

    if (view == null) {
      start(observer);
    }

    if (view != null) {
      g2 = canvas.getGraphics2D();
      canvas.setRenderingHints(g2);
      canvas.setTransform(g2);
      displayLocation(p);
      cur = view.physicalToVirtual(p);
      hidePopup(true, false);

      DrawShapeSearch.Result result = doc.search(cur);
      if (result != null) { 
	pos = result.position;
	pos1 = pos & HIGH_MASK;
	pos2 = pos & LOW_MASK;
	if (result.shape != null) { 
	  overShape = result.shape;
	} else { 
	  overShape = null;	
	}
      } else { 
	overShape = null;
      }

      if (lastOverShape != null &&
	  lastOverShape != overShape) { 
	handleOverShapeDeselected(lastOverShape);
      }

      if (debug) {
	System.out.println("PointTool.mouseMoved() @0 result.position " + 
			   (result != null ? Integer.toHexString(result.position) :
			    "result == null"));
      }

      if (result != null && 	  
	  result.shape != null &&
	  DrawShapeSearch.isInterior(result.position)) { 
	if (debug) {
	  System.out.println("PointTool.mouseMoved() @1");
	}

	if (overShape != lastOverShape) {
	  if (debug) {
	    System.out.println("PointTool.mouseMoved() @2");
	  }
	  if (showOverHint && 
	      overHintOn && 
	      overShape != null) {
	    // clear over hint 
	    canvas.repaintShape(overShape, true);
	    overHintOn = false;
	  }
	}
      } else {
	if (debug) {
	  System.out.println("PointTool.mouseMoved() @3");
	}
	if (showOverHint && 
	    overHintOn && 
	    overShape != null) {
	  // clear over hint 
	  canvas.repaintShape(overShape, true);
	}
	overHintOn = false;
      }

      if (debug) {
	if (result != null) { 
	  System.out.println("PointTool.mouseMoved() @4: result.position=" + 
			     Integer.toHexString(result.position));
	}
      }
	
      if (result != null && 
	  result.position != PointOutside) {
	if (pos1 == PointInside) {
	  if (result.shape instanceof Label && 
	      result.shape.isSticky()) {
	    view.displayHelpMessage(MSGKEY_18);
	  } else {
	    view.displayHelpMessage(MSGKEY_1);
	  }
	  canvas.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
	} else {
	  if (result.shape instanceof GraphEdgeShape) {
	    GraphEdgeShape eshape = (GraphEdgeShape) result.shape;
	    displayMessage(eshape.getEdgeShape());
	  } else {
	    displayMessage(result.shape);
	  }
	}
      } else {
	view.displayHelpMessage(MSGKEY_14);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      }

      if (result != null && 
	  result.position == PointOnMessage) {
	//System.out.println("PointTool.mouseMoved() @5a");
	handleMessagePopup(overShape);
      } else if (doc.getDocumentType() == Document.DocumentType.Visual_Class_Diagram && 
		 overShape instanceof TextShape) { 
	//System.out.println("PointTool.mouseMoved() @5b");
	handleStatementPopup(overShape);
      } else { 
	//System.out.println("PointTool.mouseMoved() @5c");
	boolean altdown = ((modifiers & InputEvent.ALT_DOWN_MASK) != 0);
	if (!handleHoverPopup(overShape, altdown)) { 
	  if (showOverHint && 
	      overShape != null &&
	      !overHintOn) { 
	    if (debug) {
	      System.out.println("PointTool.mouseMoved() @5d");
	    }
	    // draw over hint directly on screen
	    overShape.drawShape(g2, (doc instanceof GraphDocument ? 
				     DRAW_GRAPH_OVER_HINT : DRAW_OVER_HINT));
	    overHintOn = true;
	  }
	  
	  if (debug) { 
	    if (result != null) {
	      System.out.println("PointTool.mouseMoved() @6: result.position=" + 
				 Integer.toHexString(result.position));
	    } else {
	      System.out.println("PointTool.mouseMoved() @6: result == null");
	    }
	  }
	}
      }
	
    }
    lastOverShape = overShape; 
  }

  JPanelPopup messagePopup; 

  protected void handleMessagePopup(DrawShape overShape) {
    //System.out.println("PointTool.handleMessagePopup()");
    if (overShape != null) { 
      DrawShapeMessage msg = overShape.getMessage();
      if (msg != null) { 
	String message = msg.getMessage();
	if (message != null) { 
	  if (messagePopup == null) { 
	    messagePopup = new JPanelPopup();	    
	  } else { 
	    messagePopup.removeAll();
	  }
	  messagePopup.setOpaque(true);
	  messagePopup.setLayout(new BoxLayout(messagePopup, BoxLayout.Y_AXIS));
	  messagePopup.add(new JLabel("Compiler error:"));
	  if (message.indexOf('\n') < 0) { 
	    messagePopup.add(new JLabel(message));
	  } else { 
	    String[] lines = message.split("\n"); 
	    for (int i = 0; i < lines.length; i++) { 
	      messagePopup.add(new JLabel(lines[i]));
	    }
	  }
	  Border b1 = BorderFactory.createLineBorder(Color.gray);
	  Border b2 = BorderFactory.createEmptyBorder(10, 10, 10, 10);
	  messagePopup.setBorder(BorderFactory.createCompoundBorder(b1, b2));
	  messagePopup.pack();
	  view.showPopup(messagePopup, mousePosition.x, mousePosition.y);
	}
      }
    }
  }

  protected void handleStatementPopup(DrawShape overShape) { }

  protected void handleOverShapeDeselected(DrawShape prevOverShape) { 
  }

  protected void handleShapeSelected(DrawShape selectedShape) { 
  }

  protected void handleShapeDeselected(DrawShape prevSelectedShape) { 
  }

  /**
   * Return true if a popup is shown, false otherwise 
   */
  protected boolean handleHoverPopup(DrawShape overShape, boolean altdown) {
    return false;
  } 

  protected void hidePopup() {
    hidePopup(true, true);
  }

  protected void hidePopup(boolean hideThumbNail, boolean hideOverShapePopup) {
    if (debug) {
      System.out.println("PointTool.hidePopup() " +
			 hideThumbNail + ":" + hideOverShapePopup);
    }

    if (messagePopup != null && 
	messagePopup.isVisible()) { 
      view.hidePopup(messagePopup);
    }
    if (hideThumbNail) { 
      view.hideThumbNail();
    }
  }

  protected void displayMessage(DrawShape shape) { 
    if (pos1 == PointOnIcon) { 
      canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
    } else if (shape instanceof LineShape) {
      if (pos1 == PointOnEnd) {
	view.displayHelpMessage(MSGKEY_2);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      } else if (pos1 == PointInside) {
	view.displayHelpMessage(MSGKEY_1);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
      }

      //view.displayHelpMessage(MSGKEY_2);
      //canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
    } else if (shape instanceof PolyShape) {

      if (debug) {
	System.out.println("PointTool.mouseMoved() PolyShape");
      }

      if (pos1 == PointOnPolyShapeVertex) {
	if ((pos & LOW_MASK) == 0) {
	  // the anchor vertex
	  view.displayHelpMessage(MSGKEY_3);
	  canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
	} else {
	  view.displayHelpMessage(MSGKEY_4);
	  canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
	}
      } else if (pos1 == PointOnPolyShapeEdge) {
	view.displayHelpMessage(MSGKEY_5);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
      } else if (pos1 == PointOnPolyCurveCtrPoint) {
	if (debug) {
	  System.out.println("PointTool.mouseMoved() PointOnPolyCurveCtrPoint");
	}
	view.displayHelpMessage(MSGKEY_17);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      }
    } else if (shape instanceof TextShape) {
      if (shape.isSticky()) {
	view.displayHelpMessage(MSGKEY_18);
      } else {
	if (pos1 == PointOnStringBaselineEnd) {
	  view.displayHelpMessage(MSGKEY_6);
	  canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
	} else if (pos1 == PointOnStringBaselineStart) {
	  view.displayHelpMessage(MSGKEY_7);
	  canvas.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
	}
      }
    } else if (shape instanceof CurveShape) {
      if (pos1 == PointOnCurveEndPoint) {
	view.displayHelpMessage(MSGKEY_8);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      } else if (pos1 == PointOnCurveCtrPoint) {
	view.displayHelpMessage(MSGKEY_9);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      } else if (pos1 == PointOnCurve) {
	view.displayHelpMessage(MSGKEY_10);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
      }
    } else {
      boolean cornorSensitive = false;
      boolean edgeSensitive = false;
      if (shape instanceof TwoEndsShape) {
	TwoEndsShape s2 = (TwoEndsShape) shape;
	cornorSensitive = s2.isCornorSensitive();
	edgeSensitive = s2.isEdgeSensitive();
      }
      if (edgeSensitive && pos1 == PointNorthEdge) {
	view.displayHelpMessage(MSGKEY_12);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
      } else if (edgeSensitive && pos1 == PointSouthEdge) {
	view.displayHelpMessage(MSGKEY_12);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
      } else if (edgeSensitive && pos1 == PointEastEdge) {
	view.displayHelpMessage(MSGKEY_12);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
      } else if (edgeSensitive && pos1 == PointWestEdge) {
	view.displayHelpMessage(MSGKEY_12);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
      } else if (cornorSensitive && pos1 == PointNECorner) {
	view.displayHelpMessage(MSGKEY_11);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
      } else if (cornorSensitive && pos1 == PointNWCorner) {
	view.displayHelpMessage(MSGKEY_11);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
      } else if (cornorSensitive && pos1 == PointSECorner) {
	view.displayHelpMessage(MSGKEY_11);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
      } else if (cornorSensitive && pos1 == PointSWCorner) {
	view.displayHelpMessage(MSGKEY_11);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
      } else if (pos1 == PointOnCircumference) {
	view.displayHelpMessage(MSGKEY_13);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      } else if (pos1 == PointOnRotationHandle) {
	if (debug) {
	  System.out.println("PointTool.mouseMoved() PointOnRatationHandle");
	}

	view.displayHelpMessage(MSGKEY_14);
	canvas.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      } else {
	if (pos1 == PointOnHorizontalDivider) {
	  if (shape instanceof HorizontalCompartments) {
	    view.displayHelpMessage(MSGKEY_15);
	    canvas.setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
	  }
	} else if (pos1 == PointOnVerticalDivider) {
	  if (shape instanceof VerticalCompartments) {
	    view.displayHelpMessage(MSGKEY_16);
	    canvas.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
	  }
	} else if (pos1 == PointInHorizontalCompartment || 
		   pos1 == PointInVerticalCompartment) {
	  view.displayHelpMessage(MSGKEY_1);
	  canvas.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
	}
      }
    }
  }

  public void mouseRButtonPressed(Observer observer, Point p, int modifiers) {
    if (debug) {
      System.out.println("PointTool.mouseRButtonPressed p=" + p);
    }
    mousePosition = p;

  }

  public void mouseRButtonReleased(Observer observer, Point p, int modifiers) {
    if (debug) {
      System.out.println("PointTool.mouseRButtonReleased p=" + p);
    }

    mousePosition = p;

    if (!mouseButtonDown && view != null) {

    }
  }

  public void mouseEntered(Observer observer, Point p, int modifiers) {
    if (debug) {
      System.out.println("PointTool.mouseEntered() p=" + p);
    }

    start(observer);
    Dimension dim = canvas.getSize();
    if (p.x > 5 && 
	p.x < dim.width - 5 &&
	p.y > 5 && 
	p.y < dim.height - 5) {
      return; // still inside canvas 
    }
    
    mousePosition = p;
    if (view != null) {
      cur = view.physicalToVirtual(p);
      hidePopup();
    }
  }

  public void mouseExited(Observer observer, Point p, int modifiers) {
    if (debug) {
      System.out.println("PointTool.mouseExisted() p=" + p);
    }

    Dimension dim = canvas.getSize();
    if (p.x > 5 && 
	p.x < dim.width - 5 &&
	p.y > 5 && 
	p.y < dim.height - 5) {
      return; // still inside canvas 
    }

    mousePosition = p;
    if (view != null) {
      cur = view.physicalToVirtual(p);
      hidePopup();

      canvas.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
  }

  public void mouseWheelMoved(Observer observer, Point p, int wheelRotation,
			      int modifiers) {
    mousePosition = p;
    start(observer);
    if (view != null) {
      cur = view.physicalToVirtual(p);

    }
  }

  public void keyPressed(Observer observer, int keyChar, int keyCode,
			 int modifiers) {
    if (debug) {
      System.out.println("PointTool.keyPressed() keyChar=" + keyChar + 
			 "  keyCode=" + keyCode);
    }

    if (selectedShape != null && selectedShape.isEditable()) {
      // moveShape != null) {
      if (debug) {
	System.out.println("PointTool.keyPressed(): @1");
      }
      if (selectedShape instanceof TextShape) {
	if (debug) {
	  System.out.println("PointTool.keyPressed(): @2");
	}
	if (keyCode == KeyEvent.VK_DELETE && 
	    (modifiers & InputEvent.SHIFT_DOWN_MASK) != 0) {
	  if (debug) {
	    System.out.println("PointTool.keyPressed(): @3");
	  }
	  Command cmd = new RemoveShapeCommand(view, doc, selectedShape);
	  doc.executeCommand(cmd);
	} else {
	  if (debug) {
	    System.out.println("PointTool.keyPressed(): @4");
	  }
	  if (textTool != null) {
	    if (debug) {
	      System.out.println("PointTool.keyPressed(): @5");
	    }
	    textTool.keyPressed(observer, keyChar, keyCode, modifiers);
	  }
	}
      } else {
	if (debug) {
	  System.out.println("PointTool.keyPressed(): @6");
	}
	if (keyCode == KeyEvent.VK_DELETE) {
	  if (debug) {
	    System.out.println("PointTool.keyPressed(): @7");
	  }
	  Command cmd = new RemoveShapeCommand(view, doc, moveShape);
	  doc.executeCommand(cmd);
	}
      }
    }
  }

  public TextTool getTextTool() {
    return textTool;
  }

  public void setTextTool(TextTool tool) {
    textTool = tool;
  }

  public void layoutGraphEdgesAdjacentToCurrentNode() {
    if (debug) {
      System.out.println("PointTool.layoutGraphEdgesAdjacentToCurrentNode()");
    }

    if (node != null && adjacentEdges != null) {
      GraphDocument.layoutEdges(adjacentEdges);
      canvas.repaint();
    }
  }


  ////////////////////////// Auxiliary methods //////////////////////////////

  protected DrawShape search(Point p) { 
    if (view != null) { 
      Point2D cp = view.physicalToVirtual(p);
      DrawShapeSearch.Result result = doc.search(cp);
      if (result != null && 
	  result.position != PointOutside) {
	pos = result.position;
	pos1 = pos & HIGH_MASK;
	pos2 = pos & LOW_MASK;
	return result.shape; 
      }
    }
    return null;
  }

  protected void prepareEditingGraph() {
    graph = null;
    label = null;
    node = null;
    edge = null;
    src = des = null;
    editingGraph = false;
    movingNode = false;
    movingLabel = false;
    movingEdge = false;
    movingEdgeBothEnds = false;
    movingEdgeSource = false;
    movingEdgeDestination = false;
    if (moveShape != null) {
      editingGraph = ((doc instanceof GraphDocument) && 
		      doc.getDocumentType() != Document.DocumentType.Generic_Diagram);
      if (editingGraph) {
	graph = (GraphDocument) doc;
	if (moveShape instanceof Label) {
	  label = (Label) moveShape;
	  if (label.isLabel() && 
	      (moveShape.getRole() & ROLE_LABEL) != 0) {
	    movingLabel = true;
	  } else {
	    label = null;
	    movingLabel = false;
	  }
	} else if (moveShape instanceof GraphNodeBase) {
	  node = (GraphNodeBase) moveShape;
	  adjacentEdges = GraphDocument.allAdjacentEdges(node, null);
	  movingNode = true;
	  //if (moveShape instanceof GraphHyperNodeBase && 
	  if (pos1 == PointOnIcon) { 
	    onIcon = true; 
	    if (pos2 == 0) { 
	      expansionIcon = true; 
	    }
	  }
	} else if (moveShape instanceof GraphEdgeBase) {
	  edge = (GraphEdgeBase) moveShape;
	  movingEdge = true;
	  GraphNode src0 = edge.getSource();
	  GraphNode des0 = edge.getDestination();
	  if (src0 instanceof GraphNodeBase) {
	    src = (GraphNodeBase) src0;
	  }
	  if (des0 instanceof GraphNodeBase) {
	    des = (GraphNodeBase) des0;
	  }

	  GraphEdgeBase edgeShape = edge;
	  if (edge instanceof GraphEdgeShape) { 
	    GraphEdgeShape eshape = (GraphEdgeShape) edge;
	    edgeShape = eshape.getEdgeShape();
	  }
	  prepareEditingGraphEdge(edgeShape, pos1, pos2);

	  adjacentEdges = null;
	  if (movingEdgeBothEnds) {
	    if (src != null) {
	      adjacentEdges = GraphDocument.allAdjacentEdges(src, adjacentEdges);
	    }
	    // adjacentEdges = GraphDocument.allAdjacentEdges(edge,
	    // adjacentEdges);
	    if (des != null && des != src) {
	      adjacentEdges = GraphDocument.allAdjacentEdges(des, adjacentEdges);
	    }
	    Set betweenEdges = GraphDocument.edgesBetween(src, des);
	    if (betweenEdges != null) {
	      if (adjacentEdges != null) {
		adjacentEdges.addAll(betweenEdges);
	      } else {
		adjacentEdges = betweenEdges;
	      }
	    }
	  } else {
	    adjacentEdges = GraphDocument.allAdjacentEdges(edge, adjacentEdges);
	  }
	  if (adjacentEdges != null) {
	    adjacentEdges.remove(edge);
	  }
	}
      }
    }
  }

  protected void prepareEditingGraphEdge(GraphEdgeBase edgeShape, 
					 int pos1, int pos2) { 
    if (edgeShape instanceof PolyLineShape) {
      PolyLineShape pline = (PolyLineShape) edgeShape;
      int n = pline.getNumberOfPoints();
      if (pos1 == PointOnPolyShapeVertex) {
	if (pos2 == 0) {
	  movingEdgeSource = true;
	} else if (pos2 == (n - 1)) {
	  movingEdgeDestination = true;
	}
      } else if (pos1 == PointOnPolyShapeEdge) {
	movingEdgeBothEnds = true;
      }
    } else if (edgeShape instanceof CurveShape) {
      if (pos1 == PointOnCurveEndPoint) {
	if (pos2 == PointGraphEdgeSource) {
	  movingEdgeSource = true;
	} else if (pos2 == PointGraphEdgeDestination) {
	  movingEdgeDestination = true;
	}
      } else if (pos1 == PointOnCurve && 
		 edgeShape.isMovable()) {
	movingEdgeBothEnds = true;
      }
    } else if (edgeShape instanceof LineShape) {
      if (pos1 == PointOnEnd) {
	if (pos2 == PointGraphEdgeSource) {
	  movingEdgeSource = true;
	} else if (pos2 == PointGraphEdgeDestination) {
	  movingEdgeDestination = true;
	}
      } else if (pos1 == PointInside && 
		 edgeShape.isMovable()) {
	movingEdgeBothEnds = true;
      }
    } else {
      if ((pos1 == PointNECorner || 
	   pos1 == PointNWCorner || 
	   pos1 == PointSECorner || 
	   pos1 == PointSWCorner) && 
	  edgeShape.isCornorSensitive()) {
	if (pos2 == PointGraphEdgeSource) {
	  movingEdgeSource = true;
	} else if (pos2 == PointGraphEdgeDestination) {
	  movingEdgeDestination = true;
	}
      } else if (pos1 == PointInside && 
		 edgeShape.isMovable()) {
	movingEdgeBothEnds = true;
      }
    }

    if (debug) { 
      System.out.println("PointTool.prepareEditingGraphEdge(): movingEdgeSource=" + 
			 movingEdgeSource +
			 " movingEdgeDestination=" + movingEdgeDestination +
			 " movingEdgeBothEnds=" + movingEdgeBothEnds + 
			 " pos=" + Integer.toHexString(pos) + " pos2=" + pos2);
    }
  }

  protected void unselectShape() {
    DrawShape prevSelectedShape = doc.getSelectedShape();
    if (prevSelectedShape != null) {
      doc.setSelectedShape(null);
      handleShapeDeselected(prevSelectedShape);
      canvas.repaintShape(prevSelectedShape, true);
    }
    if (!doc.getSelectionSet().isEmpty()) {
      // unselect all shapes
      doc.clearSelectionSet();
      canvas.repaint();
      doc.selectionSetUpdated();
    }    
  }
  
  protected void setSelectedShape(DrawShape shape, int position, boolean moveLabel) { 
    if (shape != null) { 
      selectedShape = shape;
      pos = position;
      pos1 = pos & HIGH_MASK;
      pos2 = pos & LOW_MASK;
      
      moveShape = selectedShape;
      DrawShape parent = selectedShape.getParent();
      while (parent instanceof Group) {
	moveShape = parent;
	parent = moveShape.getParent();
      }
      
      while (moveShape != null && 
	     parent != null && 
	     (!moveShape.isMovable() || 
	      (moveShape.isSticky() && !moveLabel))) {
	moveShape = parent;
	parent = moveShape.getParent();
      }
      prepareEditingGraph();
    }
  }

  protected void setSelection(int modifiers) {
    if (view != null) {
      unselectShape();
      if (actionShape == null) { 
	//System.out.println("PointTool.setSelection() actionShape == null");
	DrawShapeSearch.Result result = doc.search(cur);
	if (result != null) { 
	  boolean moveLabel = (modifiers & InputEvent.ALT_DOWN_MASK) != 0;
	  if (result.shape.isSelectable() &&
	      result.position != PointOutside) {
	    setSelectedShape(result.shape, result.position, moveLabel);
	  } else { 
	    DrawShape shape = result.shape;
	    while (shape != null && 
		   !(shape.isSelectable())) { 
	      shape = shape.getParent();
	    }
	    if (shape != null) {
	      int rpos = result.position;
	      if ((rpos & HIGH_MASK) != PointOnIcon) { 
		rpos = DrawShapeConstants.PointInside;
	      }
	      setSelectedShape(shape, rpos, moveLabel);
	    } 
	  }
	}
      } else { 
	//System.out.println("PointTool.setSelection() actionShape != null");
	setSelectedShape(actionShape, DrawShapeConstants.PointInside,
			 (modifiers & InputEvent.ALT_DOWN_MASK) != 0);
	actionShape = null;
      }
      prev = cur;
    }
  }

  protected void unsetSelection() {
    pos = pos1 = pos2 = 0;
    selectedShape = null;
    moveShape = null;
    moveCtrPoints = false;
    graph = null;
    label = null;
    adjacentEdges = null;
    node = null;
    edge = null;
    src = des = null;
    editingGraph = false;
    movingLabel = false;
    movingNode = false;
    movingEdge = false;
    movingEdgeBothEnds = false;
    movingEdgeSource = false;
    movingEdgeDestination = false;
    expansionIcon = false; 
    onIcon = false; 
    pclip = false;
    srcpclip = false;
    despclip = false;
  }

  protected static boolean isSelfLoop(GraphEdgeBase e) { 
    if (e != null) { 
      if (e instanceof GraphEdgeShape) { 
	GraphEdgeShape eshape = (GraphEdgeShape) e;
	GraphEdgeBase e1 = eshape.getEdgeShape();
	return (e1 instanceof SelfLoop);
      } else { 
	return (e instanceof SelfLoop);
      }
    }
    return false; 
  }

  protected static void moveSlefLoop(GraphNodeBase node,
				     float dx, float dy) { 
    if (node != null) { 
      Set<GraphEdgeBase> edges = GraphDocument.allAdjacentEdges(node);
      Iterator<GraphEdgeBase> eiter = edges.iterator();
      while (eiter.hasNext()) {
	GraphEdgeBase e = eiter.next();
	if (isSelfLoop(e)) { 
	  e.move(dx, dy);
	}
      }      
    }
  }

  /**
   * Currently selected shape
   */
  protected DrawShape selectedShape;

  /**
   * Shape to be moved by mouse dragging
   *   Either == selectedShape or the parent of selectedShape  
   */
  protected DrawShape moveShape; 

  /**
   * During mouse move:
   *   the shape currently under the mouse pointer  
   */
  protected DrawShape overShape;
  protected DrawShape lastOverShape;

  protected DrawShape hotspotShape;

  /**
   * The position of the shape at the current location of the mouse:
   *     The result of doc.search 
   *     pos1: the high 16 bits     
   *     pos2: the low 16 bits     
   */
  protected int pos, pos1, pos2;

  protected boolean moveCtrPoints;

  /**
   * The current mouse position in devise (physical) space 
   */
  protected Point mousePosition;

  /**
   * The current mouse position in view (virtual) space 
   */
  protected Point2D cur;

  protected GraphDocument graph;

  protected GraphNodeBase node;

  protected GraphEdgeBase edge;

  protected GraphNodeBase src, des;

  protected Label label;

  protected Set<GraphEdgeBase> adjacentEdges;

  protected boolean editingGraph;

  protected boolean movingLabel;

  protected boolean movingNode;

  protected boolean movingEdge, movingEdgeBothEnds, movingEdgeSource, movingEdgeDestination;

  protected boolean pclip, srcpclip, despclip;

  protected boolean expansionIcon; 
  protected boolean onIcon; 

  protected Graphics2D g2;

  protected TextTool textTool;

  protected boolean showOverHint = false; 
  protected boolean overHintOn   = false; //false; 

  protected boolean dragOutline = false;

  protected java.util.List<AuxAction> auxActions = new java.util.ArrayList<AuxAction>();

  protected static final String MSGKEY_1  = "PointTool.message.1";
  protected static final String MSGKEY_2  = "PointTool.message.2";
  protected static final String MSGKEY_3  = "PointTool.message.3";
  protected static final String MSGKEY_4  = "PointTool.message.4";
  protected static final String MSGKEY_5  = "PointTool.message.5";
  protected static final String MSGKEY_6  = "PointTool.message.6";
  protected static final String MSGKEY_7  = "PointTool.message.7";
  protected static final String MSGKEY_8  = "PointTool.message.8";
  protected static final String MSGKEY_9  = "PointTool.message.9";
  protected static final String MSGKEY_10 = "PointTool.message.10";
  protected static final String MSGKEY_11 = "PointTool.message.11";
  protected static final String MSGKEY_12 = "PointTool.message.12";
  protected static final String MSGKEY_13 = "PointTool.message.13";
  protected static final String MSGKEY_14 = "PointTool.message.14";
  protected static final String MSGKEY_15 = "PointTool.message.15";
  protected static final String MSGKEY_16 = "PointTool.message.16";
  protected static final String MSGKEY_17 = "PointTool.message.17";
  protected static final String MSGKEY_18 = "PointTool.message.18";

  protected DeltaCommand command = null;

  protected static final boolean debug = false;

}
